﻿/************************************************************************
 * SMLib - State Model Library
 * Copyright (C) 2012-2013 Ali Khalili (khalili_DOT_ir_@SIGN_gmail_DOT_com)
 * **********************************************************************
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License (LGPL) published 
 * by the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version. This program is distributed without any
 * warranty. See the GNU Lesser General Public License for more details. 
 * ***********************************************************************
 * File         :     SMLib\Automata\MM\SUL_Emulator_MM.cs
 * Description  :     Emulator of an online/active system under learning/testing simulating a Mealy machine
 *                    By online, I mean that we trace the test, step by step whereas in offline systems, 
 *                    we give the input trace and the result is the complete output sequence
 * Change log   :     -24 July 2013: Adding the -limited- possibility for rare events
 * To Do        :     
 ************************************************************************/
using System;
using System.Collections.Generic;
using SMLib.Automata.Base;
using SMLib.Automata.MM;


namespace SMLib.Automata.MM
{
    /// <summary>
    /// Emulator of a System Under Learning where its underlaying machine is a (non-deterministic) Mealy machine
    /// <remarks>
    /// Two main functionality:
    ///     - Reset the system
    ///     - Obtaining -random- output providing the input
    /// </remarks>
    /// </summary>
    public class SUL_Emulator_MM : IBlackBox_MM
    {
        MealyMachine machine;
        MealyMachine.Mealy_State currentNode;
        Random rnd;

        public static bool UNIFORM_SIMULATION = true;
        public static double MIN_RAND_PROB = 0.1;

        int numberOfReset;
        
        /// <summary>
        /// Number of experiments (resets) on SUL
        /// </summary>
        public int NumberOfReset
        {
        	get
        	{
        		return numberOfReset;
        	}
        }
        
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="underlieng_machine">the underlaying Mealy machine</param>
        public SUL_Emulator_MM(MealyMachine underlieng_machine)
        {
        	numberOfReset=0;
            machine = underlieng_machine;
            currentNode = (MealyMachine.Mealy_State)machine.InitialState;
            rnd = new Random();
        }

        bool isInInputAlphabet(ISymbol st)
        {
            return machine.InputAlphabet.Contains(st);
        }


        /// <summary>
        /// Reset the system (to its initial state)
        /// </summary>
        public void Reset()
        {
            numberOfReset++;
            currentNode = (MealyMachine.Mealy_State)machine.InitialState;
        }

        /// <summary>
        /// Take one (random) step providing the input
        /// </summary>
        /// <param name="input">the input symbol</param>
        /// <returns>the resulting output symbol</returns>
        public ISymbol Step(ISymbol input)
        {
            int numberOfOptions = 0;

            //* Count number of outputs
            foreach (MealyMachine.Mealy_Transition t in currentNode.Transitions)
            {
                if (t.inLabel.Equals(input))
                {
                    numberOfOptions++;
                }
            }

            //* There is no onput corresponding this input!
            if (numberOfOptions < 1)
            {
                return null;
            }
            
            //* Return the only output
            else if (numberOfOptions == 1)
            {
                foreach (MealyMachine.Mealy_Transition t in currentNode.Transitions)
                {
                    if (t.inLabel.Equals(input))
                    {
                        currentNode = (MealyMachine.Mealy_State)t.Destination;
                        return t.outLabel;
                    }
                }
            }
            //* Select one random output among severals
            else
            {
                //* the usual uniform random simulation
                if (UNIFORM_SIMULATION)
                {
                    int randomSelected = rnd.Next(numberOfOptions);
                    int counter = 0;
                    foreach (MealyMachine.Mealy_Transition t in currentNode.Transitions)
                    {
                        if (t.inLabel.Equals(input))
                        {
                            if (counter == randomSelected)
                            {
                                currentNode = (MealyMachine.Mealy_State)t.Destination;
                                return t.outLabel;
                            }
                            counter++;
                        }
                    }
                }
                //* enabling simulation with rare events
                else
                {
                    double randomProb = rnd.NextDouble();
                    int counter = 0;

                    foreach (MealyMachine.Mealy_Transition t in currentNode.Transitions)
                    {
                        if (t.inLabel.Equals(input))
                        {
                            //* we always assume that the first nondeterministic transition is the rare ones
                            if (counter == 0)
                            {
                                if (randomProb < MIN_RAND_PROB)
                                {
                                    currentNode = (MealyMachine.Mealy_State)t.Destination;
                                    //Console.WriteLine("Rare event happened!");
                                    return t.outLabel;
                                }
                            }
                            //* now, select among other ones uniformly
                            else if ((MIN_RAND_PROB + (1 - MIN_RAND_PROB) / (numberOfOptions-1) * counter) > randomProb)
                            {
                                currentNode = (MealyMachine.Mealy_State)t.Destination;
                                return t.outLabel;
                            }
                            counter++;
                        }
                    }
                }
            }
            //* this never happens!
            return null;
        }


        /// <summary>
        /// The input alphabet of underlying machine
        /// </summary>
        public Alphabet InputAlphabet
        {
            get { return machine.InputAlphabet; }
        }

    }
}
